/*
*	This file is shared, it is used by both the engine's plugin, and by the API. 
*	It is engine specific, and contains the 2 main functions for creating Scythe scenes
*/
//					PHYSX

#include "Ogre.h"

#ifdef _SCYTHEPLUGIN
#include "..\..\physx engine plugin\ephysics.h"
#include "engine plugin.h"
#include "Vector3.h"
#else
#include "Scythe.h"
#include "nxutility.h"
#include "Stream.h"
//#include "Ogre.h"
#endif


using namespace Scythe;
//using namespace ogre;

//this handy function sets the group of a whole actor
void SetActorGroup(const NxActor *actor, NxCollisionGroup group)
{
   NxU32 nbShapes = actor->getNbShapes();
   NxShape *const*shapes = actor->getShapes();

   while (nbShapes--)
   {
       shapes[nbShapes]->setGroup(group);
   }
}
inline NxVec3 toNx(const Scythe::Vector3 &v) 
{
	return NxVec3(v.x, v.y, v.z);
}
inline NxVec3 toNx(const NxVec3 &v) 
{
	return v;
}
inline NxMat33 toNx(const Scythe::Matrix &m) 
{
	return *(NxMat33*)&m;
}
inline Scythe::Matrix toScy(const NxMat33 &m) 
{
	return *(Scythe::Matrix*)&m;
}
inline Scythe::Vector3 toScy(const NxVec3 &m) 
{
	return Scythe::Vector3(m.x, m.y, m.z);
}

//Sets up the base NxShape parameters for any Box, sphere or LSS primitive
void	createBaseShape(const PhysicsPrimitive* primitive, NxShapeDesc* shape, const PhysicsActor* act)
{
	//set position
	shape->localPose.t		= toNx(primitive->pos - act->pos);		//subtract actor pos to make it a local pos
	//set rotation
	shape->localPose.M.setColumn(0, toNx(primitive->axis[0]));
	shape->localPose.M.setColumn(1, toNx(primitive->axis[1]));
	shape->localPose.M.setColumn(2, toNx(primitive->axis[2]));

	//material
	shape->materialIndex	= primitive->materialIndex;
	shape->group		= primitive->group;
	shape->mass			= primitive->mass;
	shape->density		= primitive->density;
	shape->skinWidth		= primitive->skinWidth;
	if(primitive->fluidDrain)
		shape->shapeFlags		|= NX_SF_FLUID_DRAIN;
}

ScytheActor*	PhysicsClass::createNewActor(const PhysicsActor *act, const Scythe::Vector3 position, const sScene nxScene, const Scythe::Matrix rotation, pfnBody bodyCallbackOverride, sSpace spce, SkeletonData* skeleton)
{

	NxVec3 zero(float(0),float(0),float(0));
	NxVec3 tempVec;
	float actMass = 0;		//we use this instead of act->mass
	float totalMass = 0;

	NxActorDesc ActorDesc;

	

	NxBodyDesc body;
	//BODY
	if(act->staticEntity == false)		//static ents dont have a body
	{
		body.angularDamping	= act->angularDamping;
		body.linearDamping	= act->linearDamping;
		body.mass			= act->mass;
		body.sleepLinearVelocity	= act->sleepVelocity[0];
		body.sleepAngularVelocity	= act->sleepVelocity[1];
		if(act->disableGravity)
			body.flags			|= NX_BF_DISABLE_GRAVITY ;
		body.solverIterationCount = act->maxIterations;

		//ACTOR
		ActorDesc.body		= &body;
		ActorDesc.density		= act->density;
	}

	ActorDesc.globalPose.t  = toNx( Vector3(position.x + act->pos.x, position.y + act->pos.y, position.z + act->pos.z) );
	
	//PRIMITIVES
	//create all the basic primitive shapes to make up the actor
	int primCount = act->numPrimitives;

	//Thanks to Jabberwocky for some of this.
	vector<NxShapeDesc*> vecShapes;

	//PRIMITIVES
	//create all the basic primitive shapes to make up the actor
	for(int i = 0 ; i < primCount ; i++)
	{
		//this makes the primitives pos relative to the actor pos
		//dont do this here		act->primitives[i].pos - act->pos;

		if(act->primitives[i].type == PRIMITIVE_SPHERE) {
			NxSphereShapeDesc* sphere = new NxSphereShapeDesc;
			sphere->radius = act->primitives[i].size[0] / 2;
			//now do all the basics
			createBaseShape(&act->primitives[i], sphere, act);

			ActorDesc.shapes.pushBack(sphere);
			vecShapes.push_back(sphere);		//store for deletion later
		} else {
			if(act->primitives[i].type == PRIMITIVE_CAPSULE) {		//capsule
				NxCapsuleShapeDesc* capsule = new NxCapsuleShapeDesc;
				capsule->radius = act->primitives[i].size[0] / 2;
				capsule->height = act->primitives[i].size[1];
				//now do all the basics
				createBaseShape(&act->primitives[i], capsule, act);

				ActorDesc.shapes.pushBack(capsule);
				vecShapes.push_back(capsule);
			} else {
				if(act->primitives[i].type == PRIMITIVE_MESH)		//mesh
				{
					string filepath = pathRoot + act->primitives[i].cookedFile;
					if(act->primitives[i].meshData->isConvex)
					{
						
						NxConvexShapeDesc* convex = new NxConvexShapeDesc;
						convex->meshData = nxScene->getPhysicsSDK().createConvexMesh(UserStream(filepath.c_str(), true));
						if(convex->meshData)
						{
							createBaseShape(&act->primitives[i], convex, act);
							ActorDesc.shapes.pushBack(convex);
						}
						
						vecShapes.push_back(convex);
					} else {
						NxTriangleMeshShapeDesc* triMesh = new NxTriangleMeshShapeDesc;
						triMesh->meshData = nxScene->getPhysicsSDK().createTriangleMesh(UserStream(filepath.c_str(), true));
						if(triMesh->meshData)
						{
							if(act->primitives[i].meshData->smooth)
								triMesh->meshFlags |= NX_MESH_SMOOTH_SPHERE_COLLISIONS;
							if(act->primitives[i].meshData->doubleSided)
								triMesh->meshFlags |= NX_MESH_DOUBLE_SIDED;
							createBaseShape(&act->primitives[i], triMesh, act);
							ActorDesc.shapes.pushBack(triMesh);
						}
						ActorDesc.shapes.pushBack(triMesh);
					}
				} else {
		//		if(act->primitives[i].type == PRIMITIVE_BOX) {		//assumed
					NxBoxShapeDesc* box = new NxBoxShapeDesc;
					box->dimensions		= NxVec3(act->primitives[i].size[0] / 2, act->primitives[i].size[1] / 2, act->primitives[i].size[2] / 2);	// /2 cus theyre radius's
					//now do all the basics
					createBaseShape(&act->primitives[i], box, act);

					ActorDesc.shapes.pushBack(box);
					ActorDesc.shapes.pushBack(box);
				}
		//		}
			}
		}
	}

	if(act->disableFluidCollision)
		ActorDesc.flags |= NX_AF_FLUID_DISABLE_COLLISION;

	//------Apply rotational offset------
	Vector3 finalPos = (rotation * act->pos) + position;		//apply rotational offset to position
	ActorDesc.globalPose.M = toNx( rotation );
	ActorDesc.globalPose.t = toNx( finalPos );

	//MODELS AND LIGHTS
	//now to create the object
	NxActor* nxa = nxScene->createActor(ActorDesc);

	///clean up
	vector<NxShapeDesc*>::iterator itShape = vecShapes.begin();
	for ( ; itShape != vecShapes.end(); ++itShape ) delete *itShape;
	vecShapes.clear();

	if(nxa == 0)
		return 0;
	ScytheActor* obj = new ScytheActor(nxa);

	//manual center of mass
	if(act->manualCOM)	{
		nxa->setCMassOffsetGlobalPosition( toNx( act->centerOfMass ) );
	}

	SetActorGroup(obj->body, act->group);

	//BONE DATA
	if (skeleton)
	{
		obj->character			= skeleton;		//use the new ID, not the act->bonemodelID
		obj->boneName			= act->boneName;
		obj->boneOffsetPos		= toScy(obj->body->getGlobalPosition()) - act->bonePos;
		Scythe::Matrix boneRot;
		nxVecToMat(act->boneAxis[0], act->boneAxis[1], act->boneAxis[2], boneRot);
		obj->boneOffsetRot	= toScy(obj->body->getGlobalOrientation()) - boneRot;
		//register our existence with the character
		skeleton->bones[act->boneName] = obj;
	}
/**/


	return obj;
}




Scythe::Vector3 SkeletonData::getBonePosition(string boneName)
{
	//get the object
	ScytheActor* obj = bones[boneName];

	if(!obj)
		return Vector3(0,0,0);

	return toScy(
		obj->body->getGlobalPosition());
//	- (toScy(obj->body->getGlobalOrientation()) * characterList[charID]->bones[boneName]->boneOffsetPos);
}
Scythe::Matrix SkeletonData::getBoneOrientation(string boneName)
{
	//get the object
	ScytheActor* obj = bones[boneName];

	if(!obj)
		return Scythe::Matrix();

	return toScy(obj->body->getGlobalOrientation());
}

//it automatically takes into accout the bone-ragdoll offsets due to the way actors are created, so dont worry about that.
void SkeletonData::setBonePosition(string boneName, Scythe::Vector3 pos, Scythe::Matrix rot)
{
	//get the object
	ScytheActor* obj = bones[boneName];

	if(!obj)
		return;

	obj->body->setGlobalOrientation(toNx(rot));
	obj->body->setGlobalPosition(toNx(pos));
}


/////////////////////////////
// JOINT CALLBACK FUNCTIONS
/////////////////////////////
struct JointData
{
	float	maxForce;
	float maxTorque;
	bool selfCollision;

	bool	limitsOn;
	float	limits[2];
};
float vecLength(float* v)
{
	return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
void PhysicalObject::breakableJointTest()
{
}

void createJointBase(const PhysicsJoint* joint, NxJointDesc* desc, NxActor* act0, NxActor* act1, const Scythe::Vector3 jointPos, const Scythe::Vector3 jointDir, const Scythe::Matrix rotationOffset)
{
	desc->setToDefault();

	//set the actors, order is important
	desc->actor[0] = act0;
	desc->actor[1] = act1;

	NxVec3 vec = toNx(jointPos);
	desc->setGlobalAnchor(vec);
	vec = toNx(jointDir);
//	desc->setGlobalAxis(vec);
	desc->localAxis[0] = toNx(rotationOffset * joint->axis[1]);
	desc->localAxis[1] = toNx(rotationOffset * joint->axis[1]);
	desc->localNormal[0] = toNx(rotationOffset * joint->axis[0]);
	desc->localNormal[1] = toNx(rotationOffset * joint->axis[0]);

	//breaking
	if(joint->breakable[0] > 0)
		desc->maxForce	= joint->breakable[0];
	if(joint->breakable[1] > 0)
		desc->maxTorque	= joint->breakable[1];

	if(joint->selfCollision)
		desc->jointFlags |= NX_JF_COLLISION_ENABLED;
	
	desc->jointFlags |= NX_JF_VISUALIZATION;
}
//use the joint-> class to create a joint
sJoint		PhysicsClass::createJoint(const PhysicsJoint* joint, const sBody act0, const sBody act1, const sScene nxScene, const Scythe::Vector3 positionOffset, const Scythe::Matrix rotationOffset, sJointParam jointParam)
{
	if(act0 == 0 && act1 == 0)
		return 0;
	if(act0 == act1)
		return 0;
	
	#ifndef PI
	const float PI = 3.141592654f;
	#endif
	//offsetted joint position
	Vector3 jointPos = (rotationOffset * joint->pos) + positionOffset;
	//offsetted joint rotation
	Vector3 jointDir = rotationOffset * joint->axis[1];

	NxJoint* j = 0;

	if(joint->type == JOINT_BALL)
	{
		NxJointLimitPairDesc limitPair;
		//------------joint time
		NxSphericalJointDesc jointDesc;
		createJointBase(joint, &jointDesc, act0, act1, jointPos, jointDir, rotationOffset);

		if(joint->limitsOn)
		{
			limitPair.high.value	= (PI/180.0f)*joint->limits[0];
			limitPair.low.value		= (PI/180.0f)*joint->limits[1];
			jointDesc.twistLimit = limitPair;
			jointDesc.flags |= NX_SJF_TWIST_LIMIT_ENABLED;
			//swing limit
			NxJointLimitDesc limitDesc;
			limitDesc.value = (PI/180.0f) * joint->limits[2];
			jointDesc.swingLimit = limitDesc;
			jointDesc.flags |= NX_SJF_SWING_LIMIT_ENABLED;
		}

		if(joint->projectionMode == 1)
		{
			jointDesc.projectionMode = NX_JPM_POINT_MINDIST;
			jointDesc.projectionDistance = joint->projectionDist;
		}
		if(joint->twist.active)
		{
			jointDesc.flags |= NX_SJF_TWIST_SPRING_ENABLED;
			jointDesc.twistSpring.damper		= joint->twist.damper;
			jointDesc.twistSpring.spring		= joint->twist.spring;
			jointDesc.twistSpring.targetValue	= joint->twist.value * RAD2DEG;
		}
		if(joint->swing.active)
		{
			jointDesc.flags |= NX_SJF_SWING_SPRING_ENABLED;
			jointDesc.swingSpring.damper		= joint->swing.damper;
			jointDesc.swingSpring.spring		= joint->swing.spring;
			jointDesc.swingSpring.targetValue	= joint->swing.value * RAD2DEG;
		}
		if(joint->dist.active)
		{
			jointDesc.flags |= NX_SJF_JOINT_SPRING_ENABLED;
			jointDesc.jointSpring.damper		= joint->dist.damper;
			jointDesc.jointSpring.spring		= joint->dist.spring;
			jointDesc.jointSpring.targetValue	= joint->dist.value;
		}

		j = nxScene->createJoint(jointDesc);
//		NxRevoluteJoint* jr = joint_->isSphericalJoint();

	}
	if(joint->type == JOINT_HINGE)
	{
		NxJointLimitPairDesc limitPair;
		//------------joint time
		NxRevoluteJointDesc jointDesc;
		createJointBase(joint, &jointDesc, act0, act1, jointPos, jointDir, rotationOffset);
		///////////////////
		if(joint->projectionMode == 1)
		{
			jointDesc.projectionMode = NX_JPM_POINT_MINDIST;
			jointDesc.projectionDistance = joint->projectionDist;
			jointDesc.projectionAngle	= joint->projectionAngle;
		}
		if(joint->twist.active)
		{
			jointDesc.flags |= NX_RJF_SPRING_ENABLED;
			jointDesc.spring.damper		= joint->twist.damper;
			jointDesc.spring.spring		= joint->twist.spring;
			jointDesc.spring.targetValue	= joint->twist.value *  (PI/180.0f);// RAD2DEG;
		}
		if(joint->motor.active)
		{
//			if(joint->motor.freeSpinning)
//				jointDesc.motor.freeSpin	= 1;
//			else
//				jointDesc.motor.freeSpin	= 0;

			jointDesc.motor.maxForce	= joint->motor.maxForce;
			jointDesc.motor.velTarget	= joint->motor.targetVelocity;
			jointDesc.flags |= NX_RJF_MOTOR_ENABLED;
		}

		if(joint->limitsOn)
		{
			limitPair.high.value	= (PI/180.0f)*(joint->limits[0]);
			limitPair.low.value		= (PI/180.0f)*(joint->limits[1]);

			jointDesc.limit = limitPair;
			jointDesc.flags |= NX_RJF_LIMIT_ENABLED;
		}

		j = nxScene->createJoint(jointDesc);
	}
	if(joint->type == JOINT_SLIDING)
	{
		NxJointLimitPairDesc limitPair;
		//------------joint time
		NxPrismaticJointDesc jointDesc;
		createJointBase(joint, &jointDesc, act0, act1, jointPos, jointDir, rotationOffset);

		j = nxScene->createJoint(jointDesc);
	}
	if(joint->type == JOINT_CYLINDRICAL)
	{
		NxJointLimitPairDesc limitPair;
		//------------joint time
		NxCylindricalJointDesc jointDesc;
		createJointBase(joint, &jointDesc, act0, act1, jointPos, jointDir, rotationOffset);

		j = nxScene->createJoint(jointDesc);
	}
	if(joint->type == JOINT_FIXED)
	{
		//------------joint time
		NxFixedJointDesc jointDesc;
		createJointBase(joint, &jointDesc, act0, act1, jointPos, jointDir, rotationOffset);

		j = nxScene->createJoint(jointDesc);
	}
	if(joint->type == JOINT_DISTANCE)
	{
		//------------joint time
		NxDistanceJointDesc jointDesc;
		createJointBase(joint, &jointDesc, act0, act1, jointPos, jointDir, rotationOffset);

		if(joint->limitsOn)
		{
			jointDesc.maxDistance = joint->limits[0];
			jointDesc.minDistance = joint->limits[1];
			jointDesc.flags |= NX_DJF_MAX_DISTANCE_ENABLED;
			jointDesc.flags |= NX_DJF_MIN_DISTANCE_ENABLED;
		}
		if(joint->dist.active)
		{
			jointDesc.flags |= NX_DJF_SPRING_ENABLED;
			jointDesc.spring.damper		= joint->dist.damper;
			jointDesc.spring.spring		= joint->dist.spring;
			jointDesc.spring.targetValue	= joint->dist.value;
		}

		j = nxScene->createJoint(jointDesc);
	}
	if(joint->type == JOINT_POINTONLINE)
	{
		//------------joint time
		NxPointOnLineJointDesc jointDesc;
		createJointBase(joint, &jointDesc, act0, act1, jointPos, jointDir, rotationOffset);

		j = nxScene->createJoint(jointDesc);
	}

	return j;
}





void PhysicalObject::destroy(NxScene* nxScene) //const sScene nxScene
{
	for (int i = 0; i < joints.size(); i++)
	{
		nxScene->releaseJoint(*joints[i]);
	}
	joints.clear();

	for (int i = 0; i < models.size(); i++)
	{
		delete models[i];
	}
	models.clear();

	for (int i = 0; i < obj.size(); i++)
	{
		nxScene->releaseActor(*obj[i]->body);
		delete obj[i];
	}
	obj.clear();

	for (int i = 0; i < characterList.size(); i++)
	{
		delete characterList[i];
	}
	characterList.clear();
}





